Skip to content

Publish releases from GitHub Actions with PyPI provenance - #1802

Merged
tleonhardt merged 2 commits into
mainfrom
publish-provenance
Nov 3, 2026
Merged

tleonhardt merged 2 commits into
mainfrom
publish-provenance

Conversation

@tleonhardt

@tleonhardt tleonhardt commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Summary

PyPI only accepts PEP 740 attestations (provenance) on files uploaded by a Trusted Publisher running in CI. A local upload with an API token can't carry them, so this moves the upload into GitHub Actions.

  • New .github/workflows/publish.yml, started by hand on a release tag:
    • The build job checks the tag with scripts/validate_tag.py, runs uv build, and uploads
      dist/ as an artifact. It has read-only permissions and no uv cache.
    • The publish job downloads dist/ and uploads it with pypa/gh-action-pypi-publish, which
      signs and uploads the attestations by default. It runs in the pypi or testpypi environment,
      and it's the only job that can request an OIDC token.
    • Every action is pinned to a commit SHA, since this is the workflow that can publish.
  • make publish / make publish-test still check the tag first. They then confirm the tag on
    origin matches the local one, start the workflow with gh workflow run, and follow it with
    gh run watch --exit-status. The release checklist doesn't change.
  • uv-publish is removed from the dev dependency group because nothing uses it anymore.
  • .github/CONTRIBUTING.md documents the new flow and the one-time setup.

Already configured outside this PR

  • Trusted Publishers on PyPI and TestPyPI for publish.yml with environments pypi / testpypi
  • GitHub environments pypi and testpypi:
    • deployments only from tags matching *.*.*
    • cmd2-admins required as reviewers, with administrator bypass turned off
  • Repository ruleset "Release tags": only cmd2-admins can create, move, or delete *.*.* tags

Testing

  • make check, make test, and make docs-test pass
  • zizmor reports no findings for publish.yml
  • I ran both make targets with a fake gh to check the normal run, a missing run URL, a failed
    run, and a tag that isn't on origin
  • The workflow itself can only run after this merges. The first real check is make publish-test
    on the next release tag.

Notes

  • 4.4.0 can't get provenance after the fact: PyPI only accepts attestations as part of the
    original upload, and uploaded files can't be changed.
  • The workflow has to be on main before the next release is tagged, because gh runs the copy
    of publish.yml inside the tagged commit.
  • No CHANGELOG entry, since this only changes release tooling.

PyPI only accepts PEP 740 attestations on files uploaded by a Trusted
Publisher, so a local upload with an API token can't carry provenance.

Add a publish.yml workflow that builds the tagged release and uploads it
to PyPI or TestPyPI with pypa/gh-action-pypi-publish, which signs and
uploads attestations. The build runs in a separate job with read-only
permissions, and only the upload job can request an OIDC token.

make publish and make publish-test now start that workflow on the tag at
HEAD with the GitHub CLI and wait for the run, after checking that the
tag on origin matches the local one. uv-publish is no longer used, so
drop it from the dev dependency group.

Document the release flow and the one-time setup: the Trusted Publishers,
the pypi/testpypi environments (tag-only, cmd2-admins approval, no admin
bypass), and the ruleset limiting release tags to cmd2-admins.
@codecov

codecov Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.75%. Comparing base (8d20b7b) to head (ce7ffff).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1802   +/-   ##
=======================================
  Coverage   99.75%   99.75%           
=======================================
  Files          23       23           
  Lines        6593     6593           
=======================================
  Hits         6577     6577           
  Misses         16       16           
Flag Coverage Δ
unittests 99.75% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@tleonhardt tleonhardt self-assigned this Nov 2, 2026
@tleonhardt tleonhardt added enhancement github_actions Pull requests that update GitHub Actions code labels Nov 2, 2026
@tleonhardt
tleonhardt merged commit 63ad849 into main Nov 3, 2026
46 of 51 checks passed
@tleonhardt
tleonhardt deleted the publish-provenance branch November 3, 2026 01:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement github_actions Pull requests that update GitHub Actions code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant